﻿using Dyao.Book.Application;
using Dyao.Book.Application.Abstractions;
using Dyao.Book.Contract;
using Dyao.Book.Entity;
using Dyao.Book.Repository.Abstractions;
using Foundation.Core;
using Foundation.Spider;
using Foundation.Spider.DataFlow;
using Foundation.Spider.DataFlow.Parser;
using Foundation.Spider.Http;
using Foundation.Spider.Selector;
using Foundation.Uow;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.ComponentModel;

namespace Dyao.Book.ApplicationSpiders
{
    [DisplayName("QiDianBook")]
    public class QiDianBookSpider : Spider, IQiDianBookSpider
    {
        private readonly ISpiderManager _spiderMgr;
        public QiDianBookSpider(IOptions<SpiderOptions> options,
            DependenceServices services,
            ILogger<QiDianBookSpider> logger,
            ISpiderManager spiderMgr) : base(options, services, logger)
        {
            _spiderMgr = spiderMgr;
        }

        /// <inheritdoc cref="IQiDianBookSpider.GetBooksAsync"/>
        public async Task GetBooksAsync(CancellationToken cancellationToken)
        {
            // 添加采集请求
            var requests = new List<Request>();
            int maxLength = 50;
            for (int i = 1; i <= maxLength; i++)
            {
                var request = BuildRequest(i);
                requests.Add(request);
            }

            await ExecuteAsync(cancellationToken, async () =>
            {
                await AddRequestsAsync(requests);
            });
        }

        /// <inheritdoc cref="IQiDianBookSpider.GetBooksByPageAsync"/>
        public async Task<int> GetBooksByPageAsync(int page, CancellationToken cancellationToken)
        {
            var request = BuildRequest(page);
            var count = 0;
            await ExecuteAsync(cancellationToken, async () =>
            {
                count = await AddRequestsAsync(request);
            });
            return count;
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override async Task InitializeAsync(CancellationToken stoppingToken = default)
        {
            // 添加自定义解析
            AddDataFlow<QiDianDataParser>();
            AddDataFlow<ConsoleStorage>();

            // 错误信息
            OnRequestError += (request, response) =>
            {
                string message = $"Uri:{request.RequestUri}, ReasonPhrase:{response.ReasonPhrase}";
                _spiderMgr.PushMessageAsync(Guid.NewGuid().ToString(), OperationTypeEnum.Book, message);
            };

            // 超时请求
            OnRequestTimeout += (requests) =>
            {
                foreach (var request in requests)
                {
                    string message = $"Uri:{request.RequestUri} RequestTimeout";
                    _spiderMgr.PushMessageAsync(Guid.NewGuid().ToString(), OperationTypeEnum.Book, message);
                }
            };

            await Task.CompletedTask;
        }

        /// <summary>
        /// 构建请求
        /// </summary>
        /// <param name="page">页码</param>
        /// <returns></returns>
        public Request BuildRequest(int page)
        {
            var request = new Request($"{QiDianConstant.BaseUrl}{page}/")
            {
                // 请求超时10秒
                Timeout = 10000
            };
            request.Headers.Cookie = QiDianConstant.UserCookie;
            request.Headers.UserAgent = QiDianConstant.UserAgent;

            return request;
        }
    }

    /// <summary>
    /// 起点数据解析器
    /// </summary>
    public class QiDianDataParser : DataParser
    {
        public override Task InitializeAsync()
        {
            return Task.CompletedTask;
        }

        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="context">上下文</param>
        /// <returns></returns>
        protected override async Task ParseAsync(DataFlowContext context)
        {
            var logger = context.ServiceProvider.GetService<ILogger<QiDianDataParser>>();
            var spiderMgr = context.ServiceProvider.GetService<ISpiderManager>();
            var books = new List<BookEntity>();
            try
            {
                // 网页数据解析
                var bookNode = context.Selectable.Select(Selectors.XPath(".//div[@class='book-img-text']"));
                var bookLis = bookNode.SelectList(Selectors.XPath(".//li"));
                foreach (var news in bookLis)
                {
                    var pic = news.Select(Selectors.XPath(".//div[@class='book-img-box']/a/img/@src")).Value;
                    var infoNode = news.Select(Selectors.XPath(".//div[@class='book-mid-info']"));
                    var title = infoNode.Select(Selectors.XPath(".//h2/a"))?.Value;
                    var bookIdStr = infoNode.Select(Selectors.XPath(".//h2/a/@data-bid")).Value;
                    var authorNodes = infoNode.SelectList(Selectors.XPath(".//p[@class='author']/a")).ToList();
                    var author = authorNodes.FirstOrDefault()?.Value;
                    var author2 = infoNode.Select(Selectors.XPath(".//p[@class='author']/span[@class='name']"))?.Value;

                    var category = string.Empty;
                    var subCategory = string.Empty;

                    if (string.IsNullOrEmpty(author2))
                    {
                        category = authorNodes.Count >= 2 ? authorNodes[1]?.Value : string.Empty;
                        subCategory = authorNodes.Count >= 3 ? authorNodes[2]?.Value : string.Empty;
                    }
                    else
                    {
                        author = author2;
                        category = authorNodes.Count >= 1 ? authorNodes[0]?.Value : string.Empty;
                        subCategory = authorNodes.Count >= 2 ? authorNodes[1]?.Value : string.Empty;
                    }
                    var desc = infoNode.Select(Selectors.XPath(".//p[@class='intro']"))?.Value;
                    var book = new BookEntity()
                    {
                        Title = title,
                        Author = author,
                        CoverPicture = pic,
                        Description = desc,
                        Category = category,
                        SubCategory = subCategory,
                        SpiderUrl = context.Request.RequestUri.AbsoluteUri,
                        SourceType = BookSourceType.QiDian,
                    };

                    if (int.TryParse(bookIdStr, out int bookId))
                        book.SourceBookId = bookId;

                    books.Add(book);
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, ex.Message);
                await spiderMgr.PushMessageAsync(Guid.NewGuid().ToString(), OperationTypeEnum.Book, $"Parse Error:{ex.GetTraceMessage()}");
            }


            await BulkAddBooksAsync(context, books);
        }

        public async Task BulkAddBooksAsync(DataFlowContext context, List<BookEntity> books)
        {
            var uowMgr = context.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
            var logger = context.ServiceProvider.GetRequiredService<ILogger<QiDianDataParser>>();
            var bookRepo = context.ServiceProvider.GetRequiredService<IBookRepository>();
            using (var uow = uowMgr.Begin(isReadDb: true, requiresNew: true))
            {
                var titles = books.Select(b => b.Title).ToList();
                var existsBooks = await bookRepo.GetListByTitleAsync(titles, default);
                books.RemoveAll(b => existsBooks.Any(e => e.Title == b.Title));
            }

            if (!books.Any()) return;

            using (var uow = uowMgr.Begin(isTransactional: true))
            {
                try
                {
                    foreach (var book in books)
                    {
                        book.ID = Guid.NewGuid().ToString();
                        await bookRepo.InsertAsync(book);
                    }
                    await uow.CompleteAsync();
                }
                catch (Exception ex)
                {
                    logger.LogError($"{ex.Message}", ex);
                    await uow.RollbackAsync();
                    throw;
                }
            }
        }
    }
}
